﻿using System;
using UnityEngine;
using UnityEngine.UI;

namespace Nomo.UnityAnimationModule.FrameByFrameAnimation
{
    /// <summary>
    /// 逐帧动画器。
    /// </summary>
    [DisallowMultipleComponent]
    [RequireComponent(typeof(Image))]
    public sealed class FrameByFrameAnimator : MonoBehaviour
    {
        [SerializeField]           private FrameByFrameAnimatorRepeatMode repeatMode;
        [SerializeField]           private bool                           playOnEnable;
        [SerializeField] [Min(0F)] private float                          fps     = 10F;
        [SerializeField]           private bool                           forward = true;
        [SerializeField]           private Sprite[]                       frames  = new Sprite[0];
        private                            Image                          _image;
        private                            bool                           _isPlaying;
        private                            int                            _position;
        private                            float                          _timer;

        private void Awake()
        {
            _image = GetComponent<Image>();
        }

        private void OnEnable()
        {
            if (playOnEnable)
            {
                _isPlaying = true;
            }
        }

        private void Update()
        {
            if (!_isPlaying)
            {
                return;
            }

            if (fps <= 0F)
            {
                return;
            }

            var count = frames.Length;
            switch (count)
            {
                case 0:
                    return;
                case 1 when _isPlaying:
                    _image.sprite = frames[0];
                    return;
            }

            _timer += Time.deltaTime;
            var spf = 1F / fps; // seconds per frame
            if (!(_timer > spf))
            {
                return;
            }

            var beyondTimes = (int) (_timer / spf);
            _timer -= beyondTimes * spf;

            while (beyondTimes > 0)
            {
                switch (forward)
                {
                    case true when _position == count - 1:
                        switch (repeatMode)
                        {
                            case FrameByFrameAnimatorRepeatMode.Once:
                                _image.sprite = frames[_position];
                                _isPlaying    = false;
                                _position     = -1;
                                return;
                            case FrameByFrameAnimatorRepeatMode.Repeat:
                                _position = 0;
                                break;
                            case FrameByFrameAnimatorRepeatMode.PingPong:
                                forward   = false;
                                _position = count - 2;
                                break;
                            default:
                                throw new ArgumentOutOfRangeException();
                        }
                        break;
                    case false when _position == 0:
                        switch (repeatMode)
                        {
                            case FrameByFrameAnimatorRepeatMode.Once:
                                _image.sprite = frames[_position];
                                _isPlaying    = false;
                                _position     = count;
                                return;
                            case FrameByFrameAnimatorRepeatMode.Repeat:
                                _position = count - 1;
                                break;
                            case FrameByFrameAnimatorRepeatMode.PingPong:
                                forward   = true;
                                _position = 1;
                                break;
                            default:
                                throw new ArgumentOutOfRangeException();
                        }
                        break;
                    case true:
                        _position++;
                        break;
                    case false:
                        _position--;
                        break;
                }

                beyondTimes--;
            }

            _image.sprite = frames[_position];
        }

        /// <summary>
        /// 播放。
        /// </summary>
        public void Play()
        {
            _isPlaying = true;
            _position  = forward ? 0 : frames.Length - 1;
            if (frames.Length != 0)
            {
                _image.sprite = frames[_position];
            }
        }

        /// <summary>
        /// 暂停。
        /// </summary>
        public void Pause()
        {
            _isPlaying = false;
        }

        /// <summary>
        /// 停止。
        /// </summary>
        public void Stop()
        {
            _isPlaying = false;
            _position  = forward ? 0 : frames.Length - 1;
            if (frames.Length != 0)
            {
                _image.sprite = frames[_position];
            }
        }
    }
}
